// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROVIDER_HOST_H_
#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROVIDER_HOST_H_

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/common/content_export.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/common/request_context_frame_type.h"
#include "content/public/common/request_context_type.h"
#include "content/public/common/resource_type.h"

namespace storage {
class BlobStorageContext;
}

namespace content {

class ResourceRequestBodyImpl;
class ServiceWorkerContextCore;
class ServiceWorkerDispatcherHost;
class ServiceWorkerRequestHandler;
class ServiceWorkerVersion;
class WebContents;

// This class is the browser-process representation of a service worker
// provider. There are two general types of providers: 1) those for a client
// (windows, dedicated workers, or shared workers), and 2) those for hosting a
// running service worker.
//
// For client providers, there is a provider per document or a worker and the
// lifetime of this object is tied to the lifetime of its document or the worker
// in the renderer process. This class holds service worker state that is scoped
// to an individual document or a worker.
//
// For providers hosting a running service worker, this class will observe
// resource loads made directly by the service worker.
class CONTENT_EXPORT ServiceWorkerProviderHost
    : public NON_EXPORTED_BASE(ServiceWorkerRegistration::Listener),
      public base::SupportsWeakPtr<ServiceWorkerProviderHost> {
public:
    using GetRegistrationForReadyCallback = base::Callback<void(ServiceWorkerRegistration* reigstration)>;

    using WebContentsGetter = base::Callback<WebContents*(void)>;

    // PlzNavigate
    // Used to pre-create a ServiceWorkerProviderHost for a navigation. The
    // ServiceWorkerNetworkProvider will later be created in the renderer, should
    // the navigation succeed. |is_parent_frame_is_secure| should be true for main
    // frames. Otherwise it is true iff all ancestor frames of this frame have a
    // secure origin. |web_contents_getter| indicates the tab where the navigation
    // is occurring.
    static std::unique_ptr<ServiceWorkerProviderHost> PreCreateNavigationHost(
        base::WeakPtr<ServiceWorkerContextCore> context,
        bool are_ancestors_secure,
        const WebContentsGetter& web_contents_getter);

    enum class FrameSecurityLevel { UNINITIALIZED,
        INSECURE,
        SECURE };

    // When this provider host is for a Service Worker context, |route_id| is
    // MSG_ROUTING_NONE. When this provider host is for a Document,
    // |route_id| is the frame ID of the Document. When this provider host is for
    // a Shared Worker, |route_id| is the Shared Worker route ID.
    // |provider_type| gives additional information whether the provider is
    // created for controller (ServiceWorker) or controllee (Document or
    // SharedWorker).
    ServiceWorkerProviderHost(int render_process_id,
        int route_id,
        int provider_id,
        ServiceWorkerProviderType provider_type,
        FrameSecurityLevel parent_frame_security_level,
        base::WeakPtr<ServiceWorkerContextCore> context,
        ServiceWorkerDispatcherHost* dispatcher_host);
    virtual ~ServiceWorkerProviderHost();

    const std::string& client_uuid() const { return client_uuid_; }
    int process_id() const { return render_process_id_; }
    int provider_id() const { return provider_id_; }
    int frame_id() const;
    int route_id() const { return route_id_; }
    const WebContentsGetter& web_contents_getter() const
    {
        return web_contents_getter_;
    }

    bool is_parent_frame_secure() const
    {
        return parent_frame_security_level_ == FrameSecurityLevel::SECURE;
    }
    void set_parent_frame_secure(bool is_parent_frame_secure)
    {
        CHECK_EQ(parent_frame_security_level_, FrameSecurityLevel::UNINITIALIZED);
        parent_frame_security_level_ = is_parent_frame_secure
            ? FrameSecurityLevel::SECURE
            : FrameSecurityLevel::INSECURE;
    }

    // Returns whether this provider host is secure enough to have a service
    // worker controller.
    // Analogous to Blink's Document::isSecureContext. Because of how service
    // worker intercepts main resource requests, this check must be done
    // browser-side once the URL is known (see comments in
    // ServiceWorkerNetworkProvider::CreateForNavigation). This function uses
    // |document_url_| and |is_parent_frame_secure_| to determine context
    // security, so they must be set properly before calling this function.
    bool IsContextSecureForServiceWorker() const;

    bool IsHostToRunningServiceWorker()
    {
        return running_hosted_version_.get() != NULL;
    }

    // Returns this provider's controller. The controller is typically the same as
    // active_version() but can differ in the following cases:
    // (1) The client was created before the registration existed or had an active
    // version (in spec language, it is not "using" the registration).
    // (2) The client had a controller but NotifyControllerLost() was called due
    // to an exceptional circumstance (here also it is not "using" the
    // registration).
    // (3) During algorithms such as the update, skipWaiting(), and claim() steps,
    // the active_version and controlling_version may temporarily differ. For
    // example, to perform skipWaiting(), the registration's active version is
    // updated first and then the provider host's controlling version is updated
    // to match it.
    ServiceWorkerVersion* controlling_version() const
    {
        // Only clients can have controllers.
        DCHECK(!controlling_version_ || IsProviderForClient());
        return controlling_version_.get();
    }

    ServiceWorkerVersion* active_version() const
    {
        return associated_registration_.get() ? associated_registration_->active_version() : NULL;
    }

    ServiceWorkerVersion* waiting_version() const
    {
        return associated_registration_.get() ? associated_registration_->waiting_version() : NULL;
    }

    ServiceWorkerVersion* installing_version() const
    {
        return associated_registration_.get() ? associated_registration_->installing_version() : NULL;
    }

    // Returns the associated registration. The provider host listens to this
    // registration to resolve the .ready promise and set its controller.
    ServiceWorkerRegistration* associated_registration() const
    {
        // Only clients can have an associated registration.
        DCHECK(!associated_registration_ || IsProviderForClient());
        return associated_registration_.get();
    }

    // The running version, if any, that this provider is providing resource
    // loads for.
    ServiceWorkerVersion* running_hosted_version() const
    {
        // Only providers for controllers can host a running version.
        DCHECK(!running_hosted_version_ || provider_type_ == SERVICE_WORKER_PROVIDER_FOR_CONTROLLER);
        return running_hosted_version_.get();
    }

    // Sets the |document_url_|.  When this object is for a client,
    // |matching_registrations_| gets also updated to ensure that |document_url_|
    // is in scope of all |matching_registrations_|.
    void SetDocumentUrl(const GURL& url);
    const GURL& document_url() const { return document_url_; }

    void SetTopmostFrameUrl(const GURL& url);
    const GURL& topmost_frame_url() const { return topmost_frame_url_; }

    ServiceWorkerProviderType provider_type() const { return provider_type_; }
    bool IsProviderForClient() const;
    blink::WebServiceWorkerClientType client_type() const;

    // Associates to |registration| to listen for its version change events and
    // sets the controller. If |notify_controllerchange| is true, instructs the
    // renderer to dispatch a 'controllerchange' event.
    void AssociateRegistration(ServiceWorkerRegistration* registration,
        bool notify_controllerchange);

    // Clears the associated registration and stop listening to it.
    void DisassociateRegistration();

    void SetHostedVersion(ServiceWorkerVersion* version);

    // Returns a handler for a request, the handler may return NULL if
    // the request doesn't require special handling.
    std::unique_ptr<ServiceWorkerRequestHandler> CreateRequestHandler(
        FetchRequestMode request_mode,
        FetchCredentialsMode credentials_mode,
        FetchRedirectMode redirect_mode,
        ResourceType resource_type,
        RequestContextType request_context_type,
        RequestContextFrameType frame_type,
        base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
        scoped_refptr<ResourceRequestBodyImpl> body,
        bool skip_service_worker);

    // Used to get a ServiceWorkerObjectInfo to send to the renderer. Finds an
    // existing ServiceWorkerHandle, and increments its reference count, or else
    // creates a new one (initialized to ref count 1). Returns the
    // ServiceWorkerInfo from the handle. The renderer is expected to use
    // ServiceWorkerHandleReference::Adopt to balance out the ref count.
    ServiceWorkerObjectInfo GetOrCreateServiceWorkerHandle(
        ServiceWorkerVersion* version);

    // Returns true if |registration| can be associated with this provider.
    bool CanAssociateRegistration(ServiceWorkerRegistration* registration);

    // For use by the ServiceWorkerControlleeRequestHandler to disallow
    // new registration association while a navigation is occurring and
    // an existing registration is being looked for.
    void SetAllowAssociation(bool allow) { allow_association_ = allow; }

    // Returns true if the context referred to by this host (i.e. |context_|) is
    // still alive.
    bool IsContextAlive();

    // Dispatches message event to the document.
    void PostMessageToClient(ServiceWorkerVersion* version,
        const base::string16& message,
        const std::vector<int>& sent_message_ports);

    // Adds reference of this host's process to the |pattern|, the reference will
    // be removed in destructor.
    void AddScopedProcessReferenceToPattern(const GURL& pattern);

    // |registration| claims the document to be controlled.
    void ClaimedByRegistration(ServiceWorkerRegistration* registration);

    // Called by dispatcher host to get the registration for the "ready" property.
    // Returns false if there's a completed or ongoing request for the document.
    // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-ready
    bool GetRegistrationForReady(const GetRegistrationForReadyCallback& callback);

    // Methods to support cross site navigations.
    void PrepareForCrossSiteTransfer();
    void CompleteCrossSiteTransfer(
        int new_process_id,
        int new_frame_id,
        int new_provider_id,
        ServiceWorkerProviderType new_provider_type,
        ServiceWorkerDispatcherHost* dispatcher_host);
    ServiceWorkerDispatcherHost* dispatcher_host() const
    {
        return dispatcher_host_;
    }

    // PlzNavigate
    // Completes initialization of provider hosts used for navigation requests.
    void CompleteNavigationInitialized(
        int process_id,
        int frame_routing_id,
        ServiceWorkerDispatcherHost* dispatcher_host);

    // Sends event messages to the renderer. Events for the worker are queued up
    // until the worker thread id is known via SetReadyToSendMessagesToWorker().
    void SendUpdateFoundMessage(
        int registration_handle_id);
    void SendSetVersionAttributesMessage(
        int registration_handle_id,
        ChangedVersionAttributesMask changed_mask,
        ServiceWorkerVersion* installing_version,
        ServiceWorkerVersion* waiting_version,
        ServiceWorkerVersion* active_version);
    void SendServiceWorkerStateChangedMessage(
        int worker_handle_id,
        blink::WebServiceWorkerState state);

    // Sets the worker thread id and flushes queued events.
    void SetReadyToSendMessagesToWorker(int render_thread_id);

    void AddMatchingRegistration(ServiceWorkerRegistration* registration);
    void RemoveMatchingRegistration(ServiceWorkerRegistration* registration);

    // An optimized implementation of [[Match Service Worker Registration]]
    // for current document.
    ServiceWorkerRegistration* MatchRegistration() const;

    // Called when our controller has been terminated and doomed due to an
    // exceptional condition like it could no longer be read from the script
    // cache.
    void NotifyControllerLost();

private:
    friend class ForeignFetchRequestHandlerTest;
    friend class LinkHeaderServiceWorkerTest;
    friend class ServiceWorkerProviderHostTest;
    friend class ServiceWorkerWriteToCacheJobTest;
    friend class ServiceWorkerContextRequestHandlerTest;
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerWriteToCacheJobTest, Update_SameScript);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerWriteToCacheJobTest,
        Update_SameSizeScript);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerWriteToCacheJobTest,
        Update_TruncatedScript);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerWriteToCacheJobTest,
        Update_ElongatedScript);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerWriteToCacheJobTest,
        Update_EmptyScript);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDispatcherHostTest,
        DispatchExtendableMessageEvent);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerDispatcherHostTest,
        DispatchExtendableMessageEvent_Fail);
    FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostTest, ContextSecurity);

    struct OneShotGetReadyCallback {
        GetRegistrationForReadyCallback callback;
        bool called;

        explicit OneShotGetReadyCallback(
            const GetRegistrationForReadyCallback& callback);
        ~OneShotGetReadyCallback();
    };

    // ServiceWorkerRegistration::Listener overrides.
    void OnVersionAttributesChanged(
        ServiceWorkerRegistration* registration,
        ChangedVersionAttributesMask changed_mask,
        const ServiceWorkerRegistrationInfo& info) override;
    void OnRegistrationFailed(ServiceWorkerRegistration* registration) override;
    void OnRegistrationFinishedUninstalling(
        ServiceWorkerRegistration* registration) override;
    void OnSkippedWaiting(ServiceWorkerRegistration* registration) override;

    // Sets the controller version field to |version| or if |version| is NULL,
    // clears the field. If |notify_controllerchange| is true, instructs the
    // renderer to dispatch a 'controller' change event.
    void SetControllerVersionAttribute(ServiceWorkerVersion* version,
        bool notify_controllerchange);

    void SendAssociateRegistrationMessage();

    // Syncs matching registrations with live registrations.
    void SyncMatchingRegistrations();

    // Discards all references to matching registrations.
    void RemoveAllMatchingRegistrations();

    // Increase/decrease this host's process reference for |pattern|.
    void IncreaseProcessReference(const GURL& pattern);
    void DecreaseProcessReference(const GURL& pattern);

    void ReturnRegistrationForReadyIfNeeded();

    bool IsReadyToSendMessages() const;
    void Send(IPC::Message* message) const;

    // Finalizes cross-site transfers and navigation-initalized hosts.
    void FinalizeInitialization(int process_id,
        int frame_routing_id,
        ServiceWorkerDispatcherHost* dispatcher_host);

    std::string client_uuid_;
    int render_process_id_;

    // See the constructor's documentation.
    int route_id_;

    // For provider hosts that are hosting a running service worker, the id of the
    // service worker thread. Otherwise, |kDocumentMainThreadId|. May be
    // |kInvalidEmbeddedWorkerThreadId| before the hosted service worker starts
    // up, or during cross-site transfers.
    int render_thread_id_;

    // Unique within the renderer process.
    int provider_id_;

    // PlzNavigate
    // Only set when this object is pre-created for a navigation. It indicates the
    // tab where the navigation occurs.
    WebContentsGetter web_contents_getter_;

    ServiceWorkerProviderType provider_type_;
    FrameSecurityLevel parent_frame_security_level_;
    GURL document_url_;
    GURL topmost_frame_url_;

    std::vector<GURL> associated_patterns_;
    scoped_refptr<ServiceWorkerRegistration> associated_registration_;

    // Keyed by registration scope URL length.
    typedef std::map<size_t, scoped_refptr<ServiceWorkerRegistration>>
        ServiceWorkerRegistrationMap;
    // Contains all living registrations whose pattern this document's URL
    // starts with. It is empty if IsContextSecureForServiceWorker() is
    // false.
    ServiceWorkerRegistrationMap matching_registrations_;

    std::unique_ptr<OneShotGetReadyCallback> get_ready_callback_;
    scoped_refptr<ServiceWorkerVersion> controlling_version_;
    scoped_refptr<ServiceWorkerVersion> running_hosted_version_;
    base::WeakPtr<ServiceWorkerContextCore> context_;
    ServiceWorkerDispatcherHost* dispatcher_host_;
    bool allow_association_;

    std::vector<base::Closure> queued_events_;

    DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHost);
};

} // namespace content

#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_PROVIDER_HOST_H_
